//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <ddk.h>
#include "flashctrl.h"
#include "norflash.h"

#define HwUserSpaceBase     0x0
//
// Flash
//
class Flash : public CDeviceDriver
{
public:
    CARAPI Read(
                    /* [in] */ Int64 u64Offset,
                    /* [in] */ Int32 bytesToRead,
                    /* [out] */ MemoryBuf * pBuffer,
                    /* [out] */ IEvent * * ppCompletionEvent);

    CARAPI Write(
                    /* [in] */ Int64 u64Offset,
                    /* [in] */ const MemoryBuf & buffer,
                    /* [out] */ Int32 * pBytesWritten,
                    /* [out] */ IEvent * * ppCompletionEvent);

    CARAPI Control(
                    /* [in] */ Handle32 nControlCode,
                    /* [in] */ const MemoryBuf & inBuffer,
                    /* [out] */ MemoryBuf * pOutBuffer,
                    /* [out] */ IEvent * * ppCompletionEvent);

    virtual void Dispose();

public:
    ECode Initialize(Address physbase, UInt32 offset, UInt32 size);

private:
    Address     m_physbase;
    Address     m_virtbase;
    UInt32      m_offset;
    UInt32      m_size;
    static IDeviceDriver *m_pFlash;
    friend IDeviceDriver *CreateFlash(uint_t uDeviceNo, void *pvParameter);
};

IDeviceDriver *Flash::m_pFlash = NULL;

ECode Flash::Initialize(Address physbase, UInt32 offset, UInt32 size)
{
//    m_physbase = physbase;
    m_physbase = physbase + offset;
    m_virtbase = 0;
    m_offset = offset;
    m_size = size;

//    m_virtbase = (Address)DzKIoRemap(m_physbase, /*m_size*/32 * 1024 * 1024);
//    kprintf("Mapped 0x%x to 0x%x\n", m_physbase, m_virtbase);

    return NOERROR;
}

ECode Flash::Read(
                /* [in] */ Int64 u64Offset,
                /* [in] */ Int32 bytesToRead,
                /* [out] */ MemoryBuf * pBuffer,
                /* [out] */ IEvent * * ppCompletionEvent)
{
    if (pBuffer == NULL || !pBuffer->GetCapacity()
        || (int)bytesToRead > pBuffer->GetCapacity()
        || 0 == bytesToRead
        || m_size < u64Offset + bytesToRead) {
        return E_INVALID_ARGUMENT;
    }

//    u64Offset += m_offset;

//    kprintf("Reading 0x%x, 0x%x, 0x%x, 0x%x ...\n",
//        (unsigned long)m_virtbase,
//        (long)u64Offset,
//        (unsigned char *)(char *)ebbData,
//        bytesToRead);

//    memcpy((char *)ebbData,
//        (char *)m_virtbase + (long)u64Offset, bytesToRead);
    NorFlashReadBuffer(
        (unsigned long)m_virtbase,
        (long)u64Offset,
        (unsigned char *)pBuffer->GetPayload(),
        bytesToRead);

    pBuffer->SetUsed(bytesToRead);

    return NOERROR;
}

ECode Flash::Write(
                /* [in] */ Int64 u64Offset,
                /* [in] */ const MemoryBuf & buffer,
                /* [out] */ Int32 * pBytesWritten,
                /* [out] */ IEvent * * ppCompletionEvent)
{
    if (buffer.IsNullOrEmpty()
        || 0 == buffer.GetUsed()
        || m_size < u64Offset + buffer.GetUsed()
        || NULL == pBytesWritten) {
        return E_INVALID_ARGUMENT;
    }

//    u64Offset += m_offset;

//    kprintf("Writing 0x%x, 0x%x, 0x%x, 0x%x ...\n",
//        (unsigned long)m_virtbase,
//        (long)u64Offset,
//        (unsigned char *)(char *)buffer,
//        (long)buffer.GetUsed());

    NorFlashWriteBuffer(
        (unsigned long)m_virtbase,
        (long)u64Offset,
        (unsigned char *)buffer.GetPayload(),
        (long)buffer.GetUsed());

//    if (0 != memcmp((char *)buffer,
//        (char *)m_virtbase + (long)u64Offset, (long)buffer.GetUsed())) {
//        kprintf("*ERROR* Write flash error!\n");
//    }

    *pBytesWritten = (UInt32)buffer.GetUsed();

    return NOERROR;
}

ECode Flash::Control(
                /* [in] */ Handle32 nControlCode,
                /* [in] */ const MemoryBuf & inBuffer,
                /* [out] */ MemoryBuf * pOutBuffer,
                /* [out] */ IEvent * * ppCompletionEvent)
{
    ECode ec;
    FlashInfo *pInfo;
    UInt32 offset;
    Address userSpaceVirtBase;

    switch (nControlCode) {
        case FlashControl_Open:
        /* DzIoRemap Should only appear at drivers initialization*/
        /*ec = DzIoRemap(0, m_size, m_physbase, 0, MemoryProtection_RWIO,
                          &m_virtbase);
        if (FAILED(ec)) {
            m_virtbase = 0;
            return ec;
        }
        m_virtbase = (Address)DzKIoRemap(m_physbase, m_size);
        if (0 == m_virtbase) {
            kprintf("KIoRemap failed\n");
            return E_FAIL;
        }

        assert(0 != m_virtbase);
        kprintf("Mapped 0x%x to 0x%x\n", m_physbase, m_virtbase);*/
            break;

        case FlashControl_EraseSector:
            if (inBuffer.IsNullOrEmpty()
                || sizeof(UInt32) != (size_t)inBuffer.GetUsed()) {
                return E_INVALID_ARGUMENT;
            }

            offset = *(UInt32 *)inBuffer.GetPayload();
            offset = RoundDown2(offset, ERASE_UNIT_SIZE);

//        offset += m_offset;

//        kprintf("Erasing 0x%x, 0x%x ...\n",
//                (unsigned long)m_virtbase, offset);

            NorFlashEraseSector((unsigned long)m_virtbase, offset);

            break;

        case FlashControl_GetInfo:
            if (pOutBuffer == NULL || !pOutBuffer->GetCapacity()
                || sizeof(FlashInfo) > (size_t)pOutBuffer->GetCapacity()) {
                return E_INVALID_ARGUMENT;
            }

            pInfo = (FlashInfo *)pOutBuffer->GetPayload();
            pInfo->Size = m_size;
            pInfo->EraseUnitSize = ERASE_UNIT_SIZE;
            pOutBuffer->SetUsed(sizeof(FlashInfo));

            break;

        case FlashControl_MapToUserSpace:
            ec = DzIoRemap(HwUserSpaceBase, m_size, m_physbase, 0,
                            MemoryProtection_RW, &userSpaceVirtBase);
            if (FAILED(ec)) {
                return ec;
            }
            if (pOutBuffer && (pOutBuffer->GetCapacity() > sizeof(Address))) {
                *((Address *)pOutBuffer->GetPayload()) = userSpaceVirtBase;
                pOutBuffer->SetUsed(sizeof(Address));
            }
            break;

        default:
            return E_INVALID_OPTIONS;
    }

    return NOERROR;
}

void Flash::Dispose()
{
    delete this;
}

EXTERN IDeviceDriver * CDECL CreateFlash(uint_t uDeviceNo, void *pvParameter)
{
    Flash *pFlash;
    ECode ec;

    if (Flash::m_pFlash != NULL) {
        Flash::m_pFlash->AddRef();
        return Flash::m_pFlash;
    }

    pFlash = new Flash();
    if (NULL == pFlash) return NULL;

    ec = pFlash->Initialize(HwFlashBase, 0x00600000, 0x01a00000);
    if (FAILED(ec)) {
        delete pFlash;
        return NULL;
    }

    pFlash->m_virtbase = (Address)DzKIoRemap(pFlash->m_physbase, pFlash->m_size);
    if (0 == pFlash->m_virtbase) {
        kprintf("KIoRemap failed\n");
        delete pFlash;
        return NULL;
    }

    assert(0 != pFlash->m_virtbase);
    kprintf("Mapped 0x%x to 0x%x\n", pFlash->m_physbase, pFlash->m_virtbase);

    pFlash->AddRef();
    Flash::m_pFlash = pFlash;
    return pFlash;
}
